package com.demo2do.fms.admin.service.business.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.fms.admin.dao.business.DispatchOrderDao;
import com.demo2do.fms.admin.dao.business.FinanceDao;
import com.demo2do.fms.admin.dao.business.OtherStatusDaoSupport;
import com.demo2do.fms.admin.dao.business.RemoteMessageDaoSupport;
import com.demo2do.fms.admin.dao.business.SequenceDaoSupport;
import com.demo2do.fms.admin.dao.business.ShipmentDao;
import com.demo2do.fms.admin.dao.business.TransportCostDao;
import com.demo2do.fms.admin.dao.vehicle.TractorDao;
import com.demo2do.fms.admin.dao.vehicle.VehicleDao;
import com.demo2do.fms.admin.entity.search.business.ReimburseSearch;
import com.demo2do.fms.admin.entity.search.business.SapSearch;
import com.demo2do.fms.admin.entity.security.Login;
import com.demo2do.fms.admin.handler.support.FmsJdbcSupport;
import com.demo2do.fms.admin.service.business.BalanceService;
import com.demo2do.fms.entity.authority.Organization;
import com.demo2do.fms.entity.business.Finance;
import com.demo2do.fms.entity.business.Shipment;
import com.demo2do.fms.entity.business.balance.CostLittle;
import com.demo2do.fms.entity.business.balance.Order;
import com.demo2do.fms.entity.business.balance.ReimburseOil;
import com.demo2do.fms.entity.business.balance.RevenueLittle;
import com.demo2do.fms.entity.business.balance.SapCostDetail;
import com.demo2do.fms.entity.business.balance.SapRevenueDetail;
import com.demo2do.fms.entity.business.out.Consignor;
import com.demo2do.fms.entity.business.shipment.DispatchOrder;
import com.demo2do.fms.entity.business.shipment.TransportCost;
import com.demo2do.fms.entity.data.business.Client;
import com.demo2do.fms.entity.driver.Team;
import com.demo2do.fms.entity.vam.OperationType;
import com.demo2do.fms.entity.vam.RemoteDispatchOrder;
import com.demo2do.fms.entity.vehicle.source.Vehicle;
import com.demo2do.fms.entity.vehicle.tractor.Tractor;

/**
 * 
 * @author Ysy
 *
 */
@Service("balanceService")
public class BalanceServiceImpl implements BalanceService {
    
    private static final Logger logger = LoggerFactory.getLogger(BalanceServiceImpl.class);
    
    @Autowired
    private GenericDaoSupport genericDaoSupport;
    
    @Autowired
    private FmsJdbcSupport fmsJdbcSupport;
    
    @Autowired
    private RemoteMessageDaoSupport remoteMessageDaoSupport;
    
    @Autowired
    private SequenceDaoSupport sequenceDaoSupport;
    
    @Autowired
    private OtherStatusDaoSupport otherStatusSupport;
    
    @Dao
    private FinanceDao financeDao;
    
    @Dao
    private ShipmentDao shipmentDao;
    
    @Dao
    private DispatchOrderDao dispatchOrderDao;
    
    @Dao
    private TransportCostDao transportCostDao;
    
    @Dao
    private VehicleDao vehicleDao;
    
    @Dao
    private TractorDao tractorDao;

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#accomplishConfirm(java.lang.Long, java.util.List, java.lang.Integer, com.demo2do.fms.admin.entity.security.Login)
     */
    public void accomplishConfirm(Long dispatchOrderId, List<Long> itemIds, Integer itemQuantity, Login operator) {
        
        Date now = new Date();
        
        // 1. 更新小订单的回单确认状态
        List<Shipment> selectedShipments = shipmentDao.listByItemIds(itemIds);  // 拿到所有勾选的商品车小订单
        this.accomplishConfirmShipment(itemIds, selectedShipments, operator.getName(), now);
        
        // 2. 更新派车单的回单数量, 并返回是否派车单是否全部回单结果
        DispatchOrder dispatchOrder = genericDaoSupport.load(DispatchOrder.class, dispatchOrderId);
        boolean allAccomplishConfirm = this.accomplishConfirmDispatchOrder(dispatchOrder, itemQuantity, operator.getId(), now);
        
        // 3. 判断派车单的完成状态、如果已报销更新小订单信息
        if(allAccomplishConfirm) {
            this.finishDispatchOrder(dispatchOrder, operator, now);
        }
        
    }
    
    /**
     * accomplish confirm shipment
     * 
     * 回单确认    - 更新小订单的回单确认状态 
     *       - 回写上游系统表
     * 
     * @param shipments
     * @param itemIds
     * @param operator
     */
    private void accomplishConfirmShipment(List<Shipment> shipments, String operator, Date confirmTime) {
        
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(shipments)) {
            
            for (Shipment shipment : shipments) {
                
                // 1. 更新每个小订单的回单确认状态
                genericDaoSupport.update(shipment.accomplishConfirm(confirmTime, operator, confirmTime));
                
                // 2. 写回单确认RemoteMessage到上游系统
                remoteMessageDaoSupport.accomplishConfirm(shipment.getShipmentId(), shipment.getLoadId(), operator, shipment.getLoadNumber());
            }
            
        }
        
    }
    
    /**
     * accomplish confirm shipment
     * 
     * 回单确认    - 更新小订单的回单确认状态 
     *       - 回写上游系统表
     * 
     * @param shipments
     * @param itemIds
     * @param operator
     */
    private void accomplishConfirmShipment(List<Long> itemIds, List<Shipment> shipments, String operator, Date confirmTime) {
        
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(itemIds)) {
            
            // 1. 更新每个小订单的回单确认状态
            shipmentDao.accomplishConfirm(itemIds, confirmTime, operator, confirmTime);
            
            // 2. 写回单确认RemoteMessage到上游系统
            for (Shipment shipment : shipments) {
                remoteMessageDaoSupport.accomplishConfirm(shipment.getShipmentId(), shipment.getLoadId(), operator, shipment.getLoadNumber());
            }
            
        }
        
    }    
    
    /**
     * 更新派车单的回单状态
     *              - 更新派车单回单数量
     *              - 检查派车单所有回单的数量
     * 
     * @param dispatchOrder
     * @param itemQuantity
     */
    private boolean accomplishConfirmDispatchOrder(DispatchOrder dispatchOrder, Integer itemQuantity, Long operatorId, Date confirmTime) {
        
        // 0. 派车单已全部回单
        if (dispatchOrder.getAccomplishConfirm()) {
            return true;
        }
        
        // 1. 总回单数量(勾选小车数 + 已回单数量)
        int accomplishConfirmNumber = dispatchOrder.getAccomplishConfirmNumber() != null ? dispatchOrder.getAccomplishConfirmNumber() : 0;
        int totalAccomplishConfirmNumber = itemQuantity + accomplishConfirmNumber; 
        
        // 2. 更新派车单回单数量
        genericDaoSupport.update(dispatchOrder.accomplishConfirm(totalAccomplishConfirmNumber, operatorId, confirmTime));
        
        // 3. 派车单调度数量
        int actualLoadNumber = dispatchOrder.getActualLoadNumber() != null ? dispatchOrder.getActualLoadNumber() : 0;
        
        return totalAccomplishConfirmNumber == actualLoadNumber;
        
    }    

    /**
     * 完成派车单
     * 
     * @param dispatchOrder
     * @param itemQuantity
     * @param operator
     * @param confirmTime
     */
    private void finishDispatchOrder(DispatchOrder dispatchOrder, Login operator, Date confirmTime) {
        
        Long dispatchOrderId = dispatchOrder.getId();
        
        // 1. 更新派车单的回单确认信息
        genericDaoSupport.update(dispatchOrder.finish(operator.getId(), confirmTime));
        
        // 2. 检查派车单是否报销
        if(dispatchOrder.getExpenseReimbursed()){
            
            // 2.1 置小订单为运输完成状态
            shipmentDao.finish(dispatchOrderId, operator.getName(), confirmTime);
            
            // 2.2 对于不同的小订单，写RemoteMessage给上游表
            List<Shipment> shipments = shipmentDao.listByDispatchOrderId(dispatchOrderId);
            
            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(shipments)) {
                
                Set<String> loadNumbers = new LinkedHashSet<String>();
                
                for(Shipment shipment : shipments){
                    
                    if(!loadNumbers.contains(shipment.getLoadNumber())) {
                        
                        loadNumbers.add(shipment.getLoadNumber());
                        
                        // 写调度指令完成remoteMessage到上游表
                        remoteMessageDaoSupport.complateDispatch(shipment.getLoadId(), shipment.getDispatchOrderId(), operator.getName(), shipment.getLoadNumber());
                        
                    }
                }
            }
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#listSap(com.demo2do.fms.admin.entity.search.business.SapSearch)
     */
    public List<Map<String, Object>> listSap(SapSearch search) {
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        RowMapper<Map<String, Object>> rowMapper = search.buildRowMapper();
        return fmsJdbcSupport.queryForList(sentence, parameters, rowMapper);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#sendOrderToSap(java.util.List)
     */
    public void deliverOrder(List<Long> shipmentFinanceIds) {
        
        // 1. 过滤已经送财务的收入
        List<Long> noSentShipmentFinanceIds = new ArrayList<Long>();
        List<Finance> finances = this.filterSentFinance(shipmentFinanceIds, noSentShipmentFinanceIds);
        
        // 2. 迭代过滤后的收入，进行成本的计算与创建
        String sessionId = this.getSessionId();
        for (Finance finance : finances) {
            
            // 2.1 获取当前订单下，所有子订单收入
            // 当前订单收入为finance
            List<Finance> childFinances = financeDao.listChildByShipmentFinance(finance.getShipmentFinanceId());
        
            // 2.2 子订单不为空时，将子订单成本送财务
            if (CollectionUtils.isNotEmpty(childFinances)) {
                for (Finance childFinance : childFinances) {
                    if (childFinance.getCustomerId() != null && childFinance.getConsignorId() == null) {
                        this.childCostSap(finance, childFinance, 2, sessionId);
                    } else {
                        this.childCostSap(finance, childFinance, 1, sessionId);
                    }
                }
            }
            
            // 3. 计算成本送财务
            this.revenue(finance, sessionId, 1);
            
        }
        
        // 4. 更新Finance的状态
        financeDao.updateCollectStatus(noSentShipmentFinanceIds, "S", sessionId);
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#getOrderStatus(java.lang.Long, java.lang.Long)
     */
    public int getOrderStatus(Long financeId, Long shipmentFinanceId) {
        return otherStatusSupport.getOrderStatus(financeId, shipmentFinanceId);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#coustReimburse(com.demo2do.fms.admin.entity.search.business.ReimburseSearch)
     */
    public int countReimburse(ReimburseSearch search) {
        String sentence = search.buildCountTemplate();
        Map<String, Object> parameters = search.buildParameters();
        return fmsJdbcSupport.queryForInt(sentence, parameters);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#listReimburse(com.demo2do.fms.admin.entity.search.business.ReimburseSearch, com.demo2do.core.entity.page.Page)
     */
    public List<Map<String, Object>> listReimburse(ReimburseSearch search) {
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Map<String, Object>> result = fmsJdbcSupport.queryForList(sentence, parameters, search.buildRowMapper());
        return result;
        
    }
    
    /*
     * (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.BalanceService#reimburse(java.util.List, java.util.List, com.demo2do.fms.admin.entity.security.Login)
     */
    public void reimburse(List<Long> dispatchOrderIds, List<String> shipmentOrderItems, Login operator) {

        Date now = new Date();
        
        // ============== 报销逻辑   ============
        
        // 1. 检查派车单的报销状态
        List<DispatchOrder> dispatchOrders = dispatchOrderDao.list(dispatchOrderIds);
        this.validateDispatchOrders(dispatchOrders);
        
        // 2. 对勾选的派车单进行报销操作
        for (DispatchOrder dispatchOrder : dispatchOrders) {
            genericDaoSupport.update(dispatchOrder.reimburse(operator.getId(), now));
        }
        
        // 3. 设置已报销派车单实际内部用油量
        this.createReimburseOil(dispatchOrders, operator.getName(), now);
        
        // ============== 回单逻辑   ============

        List<Long> itemIds = new ArrayList<Long>();
        Map<Long, Integer> dispatchOrderQuantityMappings = new LinkedHashMap<Long, Integer>();
        
        if (CollectionUtils.isNotEmpty(shipmentOrderItems)) {
            
            // 0. prepare data
            this.splitShipmentOrderItem(shipmentOrderItems, itemIds, dispatchOrderQuantityMappings);
            
            // 1. 更新所有小订单回单确认状态
            List<Shipment> selectedShipments = shipmentDao.listByItemIds(itemIds);  // 拿到所有的商品车小订单
            this.accomplishConfirmShipment(selectedShipments, operator.getName(), now);
        
        }
        
        for(DispatchOrder dispatchOrder : dispatchOrders) {

            boolean allAccomplishConfirm = false;
            
            Integer itemQuantity = dispatchOrderQuantityMappings.get(dispatchOrder.getId());
            itemQuantity = itemQuantity != null ? itemQuantity : 0;
            
            // 2. 更新每个派车单的回单数量
            allAccomplishConfirm = this.accomplishConfirmDispatchOrder(dispatchOrder, itemQuantity, operator.getId(), now);

            // 3. 检查每个派车单的完成状态、如果已报销更新小订单信息
            if(allAccomplishConfirm) {
                this.finishDispatchOrder(dispatchOrder, operator, now);
            }
            
        }
        
        // ================= 送财务 =====================

        // 1. 计算报销成本，送财务系统
        this.sendCostToSap(dispatchOrders);

        
        // ================= 发送派车单数据至VAM =====================
        for (DispatchOrder dispatchOrder : dispatchOrders) {
            Vehicle vehicle = genericDaoSupport.load(Vehicle.class, dispatchOrder.getVehicleId());
            Tractor tractor = tractorDao.getByBrandCNCode(vehicle.getBrandCNCode());
            if (tractor != null && tractor.isVam()) {
                RemoteDispatchOrder remoteDispatchOrder = new RemoteDispatchOrder(OperationType.CREATE, dispatchOrder.getId());
                genericDaoSupport.save(remoteDispatchOrder);
            }
        }
        
    }
    
    /**
     * validate dispatch_orders
     * 
     * @param dispatchOrders
     */
    private void validateDispatchOrders(List<DispatchOrder> dispatchOrders){
        
        // 验证派车单报销状态
        for (DispatchOrder dispatchOrder : dispatchOrders) {
            if (dispatchOrder.reimbursed()) {
                throw new BusinessException("400", "重复报销！报销失败！");
            }
        }
        
    }
    
    /**
     * 已报销派车单实际内部用油量
     * 
     * @param dispatchOrders
     * @param loginName
     */
    private void createReimburseOil(List<DispatchOrder> dispatchOrders, String loginName, Date reimburseDate) {
        
        for (DispatchOrder dispatchOrder : dispatchOrders) {
            
            TransportCost transportCost = transportCostDao.load(dispatchOrder.getId());
            
            ReimburseOil reimburseOil = new ReimburseOil(dispatchOrder, transportCost, loginName, reimburseDate); 
            genericDaoSupport.save(reimburseOil);
                    
        }
        
    }
    
    /**
     * SPLIT shipment_order_items TO item_ids AND item_quantity 
     * 
     * @param shipmentOrderItems
     * @param itemIds
     * @param dispatchOrderQuantityMappings
     */
    private void splitShipmentOrderItem(List<String> shipmentOrderItems, List<Long> itemIds, Map<Long, Integer> dispatchOrderQuantityMappings){
        
        for(String shipmentOrderItem : shipmentOrderItems) {
            
            String items[] = shipmentOrderItem.split("-");
            
            itemIds.add(Long.valueOf(items[0]));  // 小订单号  - 小车
            
            Long comfirmDispatchOrderId = Long.valueOf(items[1]);   // 小订单所对应的派车单id
            
            if(!dispatchOrderQuantityMappings.containsKey(comfirmDispatchOrderId)) {
                
                dispatchOrderQuantityMappings.put(comfirmDispatchOrderId, 1);
                
            } else {
                
                Integer confirmedQuantities = dispatchOrderQuantityMappings.get(comfirmDispatchOrderId) + 1; // 在原数量上加1
                
                dispatchOrderQuantityMappings.replace(comfirmDispatchOrderId, confirmedQuantities);
                
            }
            
        }
    }
    
    /**
     * 过滤已送财务的收入
     * 
     * @param shipmentFinanceIds
     * @return
     */
    private List<Finance> filterSentFinance(List<Long> shipmentFinanceIds, List<Long> noSentShipmentFinanceIds) {
        
        // 1. 检查是否有有效收入
        List<Finance> allFinance = financeDao.listByShipmentFinance(shipmentFinanceIds);
        if (CollectionUtils.isEmpty(allFinance)) {
            throw BusinessException.newInstance("500", "未发现有效收入!");
        }
        
        // 2. 过滤已送财务的收入
        List<Finance> finances = new ArrayList<Finance>();
        for (Finance finance : allFinance) {
            if (!StringUtils.equals("S", finance.getCollectStatus())) {
                finances.add(finance);  // 未送财务的收入
                noSentShipmentFinanceIds.add(finance.getShipmentFinanceId());   // 未送财务的ShipmentFinanceId
            }
        }
        
        return finances;
        
    }

    /**
     * 送子订单成本入财务
     * 
     * @param parentFinance
     * @param childFinance
     * @param type
     */
    private void childCostSap(Finance parentFinance, Finance childFinance, int type, String sessionId) {
        
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        
        SapCostDetail costDetail = new SapCostDetail();
        
        // 0. define some variables
        Client client = null;
        Client client2 = null;
        Consignor consignor = null;
        Organization companyOrganization = null;
        Organization consignorOrganization = null;
        
        // 1. load revenueLittle、parentShipment、shipment
        RevenueLittle revenueLittle = genericDaoSupport.load(RevenueLittle.class, childFinance.getId());
        
        Long childShipmentId = shipmentDao.getShipmentIdByFinanceId(childFinance.getId());
        if (childShipmentId == null) {
            logger.error("#deliverOrder() - 子Finance（shipmentFinanceId={}）对应Shipment不存在", childFinance.getShipmentFinanceId());
            throw BusinessException.newInstance("500", "上游Finance（" + childFinance.getShipmentFinanceId() + "）对应的Shipment不存在，请联系上游系统（OTMS/TMS）处理。");
        }
        
        Long parentShipmentId = shipmentDao.getShipmentIdByFinanceId(parentFinance.getId());
        if (parentShipmentId == null) {
            logger.error("#deliverOrder() - 父Finance（shipmentFinanceId={}）对应Shipment不存在", parentFinance.getShipmentFinanceId());
            throw BusinessException.newInstance("500", "上游Finance（" + parentFinance.getShipmentFinanceId() + "）对应的Shipment不存在，请联系上游系统（OTMS/TMS）处理。");
        }
        
        Shipment childShipment = genericDaoSupport.load(Shipment.class, childShipmentId);
        Shipment shipment = genericDaoSupport.load(Shipment.class, parentShipmentId); 
        
        
        // 2. init defined variables
        if (parentFinance.getCustomerId() != null) {
            client = genericDaoSupport.load(Client.class, parentFinance.getCustomerId());
        } else {
            consignor = genericDaoSupport.load(Consignor.class, parentFinance.getConsignorId());
            client2 = genericDaoSupport.load(Client.class, childShipment.getCustomerId()); 
        }
        
        companyOrganization = genericDaoSupport.load(Organization.class, childFinance.getCompanyId());
        if (childFinance.getConsignorId() != null) {
            consignorOrganization = genericDaoSupport.load(Organization.class, childFinance.getConsignorId());
        }
        
        // 3.1 调度指令号
        costDetail.editLoadNumber(childFinance.getLoadNumber());
        if (type == 1) {
            
            if (StringUtils.isBlank(childFinance.getLoadNumber())) {
                Shipment firstShipment = shipmentDao.loadByShipmentId(shipment.getFirstShipmentId());
                if (firstShipment != null) {
                    costDetail.editLoadNumber(firstShipment.getLoadNumber());
                }
            }
            
        } else if (type == 2) {
            
            if (StringUtils.isNotBlank(childFinance.getLoadNumber())) {
                Shipment firstShipment = shipmentDao.loadByShipmentId(shipment.getFirstShipmentId());
                if (firstShipment != null) {
                    costDetail.editLoadNumber(firstShipment.getLoadNumber());
                }
            }
            
        }
        
        if (shipment != null) {
            
            // 3.2 原始订单号
            costDetail.editShipmentOrderId(shipment.getOrderId().toString());
            
            // 3.3 调度车辆vin码
            costDetail.editSvin(shipment.getVinCode());
            
            // 3.4 派车单
            costDetail.editDispatchNumber(shipment.getLoadNumber());
            
            // 3.5 车队名称
            DispatchOrder dispatchOrder = shipment.getDispatchOrderId() != null ? genericDaoSupport.load(DispatchOrder.class, shipment.getDispatchOrderId()) : null;
            if (dispatchOrder != null) {
                Vehicle vehicle = genericDaoSupport.load(Vehicle.class, dispatchOrder.getVehicleId());
                if (vehicle != null) {
                    Team team = genericDaoSupport.load(Team.class, vehicle.getTeamId());
                    if (team != null) {
                        costDetail.editVehicleTeamName(team.getName());
                    }
                }
            }
            
            // 3.6 订单创建日期
            costDetail.setAyddrq(DateFormatUtils.format(shipment.getShipmentDate(), "yyyyMMdd"));
            
        }
        
        // 3.7 制造商
        costDetail.editManufactor(revenueLittle.getVmCode());
        
        // 3.8 运输成本
        String aycfxs = decimalFormat.format(revenueLittle.getKilo());
        String aycfzz = decimalFormat.format(revenueLittle.getKilo());
        String aycbzj = decimalFormat.format(childFinance.getFee());
        costDetail.editTransportCost("0", "0", aycfxs, aycfzz, "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", aycbzj);
        
        // 3.9 重车工资、空车工资
        costDetail.editVehicleSalary("0", "0");
        
        // 司机工资
        costDetail.editDriverSalary("0", "0");
        
        // 3.10 油耗
        costDetail.editOilUsage("0", "0");
        
        // 3.11 行驶公里数、重载公里数
        String ayxsgl = decimalFormat.format(revenueLittle.getKilo());
        String ayzzgl = decimalFormat.format(revenueLittle.getKilo());
        costDetail.editDistance(ayxsgl, ayzzgl);
        
        // 3.2 起始地目的地信息
        costDetail.editLocation(revenueLittle);
        
        // 3.13 运输方式
        String ayysfs = revenueLittle.getSmTt();
        String ayysfsmc = revenueLittle.getSmTtname();
        costDetail.editTransType(ayysfs, ayysfsmc);
        
        // 3.14 公司代码
        String bukrs = consignorOrganization != null ? consignorOrganization.getCode() : "";
        costDetail.editOrganization(bukrs);
        
        // 3.15 外包车辆所属公司
        String aywgdm = childFinance.getCompanyId() != null ? companyOrganization.getCode() : "";
        costDetail.editOutVehicleOrganization(aywgdm);
        
        // 3.16 托运客户
        String aykhdm = null;
        if (client != null) {
            aykhdm = client.getCode();
        } else {
            aykhdm = consignor != null ? consignor.getCode() : "";
        }
        costDetail.editClient(aykhdm);
        
        // 3.17 业内、业外标识
        if (client != null) {
            String aynwbz = client.getInternal() ? "X" : "";
            costDetail.editBusinessIndentify(aynwbz);
        } else {
            if (client2 != null) {
                String aynwbz = client2.getInternal() ? "X" : "";
                costDetail.editBusinessIndentify(aynwbz);
            }
        }
        
        // 3.18 外包标识
        costDetail.editOutsourceIndentify("Y");
        
        // 3.19 是否禁用
        costDetail.disable(false);
        
        // 3.20 传入财务信息
        String ayhdrq = DateFormatUtils.format(new Date(), "yyyyMMdd");
        String zydttq = DateFormatUtils.format(new Date(), "HH:mm:ss");
        
        Long financeId = childFinance.getShipmentFinanceId();
        costDetail.edit(financeId, ayhdrq, zydttq);
        
        // 3.21 
        costDetail.editClsl("1");
        
        // 3.22 sessionId
        costDetail.editSessionId(sessionId);
        
        costDetail.editVin(childFinance.getFirstShipmentId().toString());
        
        genericDaoSupport.save(costDetail);
        
    }
    
    /**
     * 计算报销成本，送财务系统
     * 
     * @param dispatchOrders
     */
    private void sendCostToSap(List<DispatchOrder> dispatchOrders) {
        
        // 2. 拆分运输成本至每辆小车
        String sessionId = this.getSessionId();
        
        for (DispatchOrder dispatchOrder : dispatchOrders) {
            
            if (dispatchOrder.isEmptyDispatch()) {
                this.emptyExpense(dispatchOrder, sessionId);      // 空车分摊
            } else {
                this.baseExpense(dispatchOrder, sessionId);       // 重车分摊
            }
            
        }
        
    }
    
    /**
     * get session id
     * 
     * @return
     */
    private String getSessionId() {
        
        try {
            
            String sessionId = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
            
            // 1. get sequence by sequenceSupport
            String sequence = sequenceDaoSupport.getSapSequence("SAP_CODE");
            Long code = Long.valueOf(sequence);
            
            // 2. construct sequence
            if (code < 10) {
                sessionId = sessionId + "00" + String.valueOf(code);
            } else if (code < 100) {
                sessionId = sessionId + "0" + String.valueOf(code);
            } else {
                sessionId = sessionId + String.valueOf(code);
            }
            
            return sessionId;
            
        } catch (Exception e) {
            logger.error("#getSessionId() - get session id fail, exception is [{}]", e);
            throw BusinessException.newInstance("500", "获取sessionId失败!业务操作失败!");
        }
        
    }
    
    /**
     * 空车分摊报销
     * 
     * @param dispatchOrderId
     */
    private void emptyExpense(DispatchOrder dispatchOrder, String sessionId) {
        
        Long dispatchOrderId = dispatchOrder.getId();
        
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        
        // 1. get dispatch order / transportCost / shipments
        TransportCost transportCost = transportCostDao.load(dispatchOrderId);
        List<Shipment> shipments = shipmentDao.listByDispatchOrderId(dispatchOrderId);
        if (CollectionUtils.isEmpty(shipments)) {
            logger.error("#emptyExpense() - 派车单对应的调度指令被删除， 派车单标识code={}", dispatchOrder.getCode());
            throw BusinessException.newInstance("500", "派车单[" + dispatchOrder.getCode() + "]对应的调度指令被删除，请联系上游系统管理员处理。");
        }
        Shipment shipment = shipments.get(0);
        
        // 2. get full salary / empty salary / normal salary
        BigDecimal otherFee = BigDecimal.ZERO;
        BigDecimal totalFullSalary = transportCost.getMainDriverFullSalary().add(transportCost.getFirstViceDriverFullSalary());
        BigDecimal totalEmptySalary = transportCost.getMainDriverEmptySalary().add(transportCost.getFirstViceDriverEmptySalary());
        BigDecimal totalSalary = transportCost.getMainDriverSalary().add(transportCost.getFirstViceDriverSalary());
        
        // 3. construct cost detail and save to database
        SapCostDetail costDetail = this.createSapCostDetail(shipment.getId());
        
        // 4. initialize cost detail when empty expense
        otherFee = new BigDecimal(costDetail.getAyqtbt());
        
        // edit session id
        costDetail.editSessionId(sessionId);
        
        // 4.1 派车单、调度单号
        String aypcd = dispatchOrder.getCode();
        String ayhddd = shipment.getLoadNumber();
        costDetail.editShipmentOrder(aypcd, ayhddd);
        
        // 4.2 制造商
        costDetail.editManufactor("");
        
        // 4.3 委托方
        costDetail.editClient("");
        
        // 4.4 重车工资、空车工资
        String ayzjzcgz = decimalFormat.format(0);
        String ayzjkcgz = decimalFormat.format(totalSalary);
        costDetail.editVehicleSalary(ayzjzcgz, ayzjkcgz);
        
        // 4.5 运输成本
        String aygzkj = decimalFormat.format(totalEmptySalary);
        String aygzdj = decimalFormat.format(totalFullSalary);
        String aycfxs = decimalFormat.format(dispatchOrder.getFacilityDistance());
        String aycfzz = decimalFormat.format(0);
        
        String aynbyl = decimalFormat.format(transportCost.getInternalOilUsage());
        String aynbyf = decimalFormat.format(new BigDecimal(aynbyl).multiply(transportCost.getInternalOilPrice()));
        String aywbyl = decimalFormat.format(transportCost.getOilUsage().subtract(transportCost.getInternalOilUsage()));
        String ayybyf = decimalFormat.format(transportCost.getExternalOilFee());
        String aynbbx = decimalFormat.format(transportCost.getInternalOilUsageCompen());
        String aynbbxf = decimalFormat.format(transportCost.getInternalOilUsageCompen());
        String ayhzyf = decimalFormat.format(new BigDecimal(aynbyf).add(new BigDecimal(ayybyf)));
        String aylqfy = decimalFormat.format(transportCost.getRoadFee());
        String ayxldj = decimalFormat.format(transportCost.getRepairFee());
        String ayltdj = decimalFormat.format(transportCost.getTyreFee());
        String ayqtbt = decimalFormat.format(otherFee);
        String aycbzj = decimalFormat.format(new BigDecimal(aylqfy).add(new BigDecimal(ayhzyf)).add(new BigDecimal(ayxldj))
                                                                   .add(new BigDecimal(ayltdj)).add(new BigDecimal(ayzjzcgz))
                                                                   .add(new BigDecimal(ayzjkcgz)).add(new BigDecimal(ayqtbt)));
        costDetail.editTransportCost(aygzdj, aygzkj, aycfxs, aycfzz, aynbyl, aynbyf, aywbyl, ayybyf, aynbbx, aynbbxf, ayhzyf, aylqfy, ayxldj, ayltdj, ayqtbt, aycbzj);
        
        // 4.6 外包标识
        String aywgbz = "N";
        costDetail.editOutsourceIndentify(aywgbz);
        
        // 4.7 更新传入财务信息等
        Long financeId = 0L;
        String ayhdrq = DateFormatUtils.format(new Date(), "yyyyMMdd");
        String zydttq = DateFormatUtils.format(new Date(), "HH:mm:ss");
        costDetail.edit(financeId, ayhdrq, zydttq);
        
        genericDaoSupport.save(costDetail);
        
    }
    
    /**
     * 重车分摊报销
     * 
     * @param dispatchOrderId
     */
    private void baseExpense(DispatchOrder dispatchOrder, String sessionId) {
        
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        
        Long dispatchOrderId = dispatchOrder.getId();
        
        // 1. get dispatch order / transportCost / shipments / finance
        TransportCost transportCost = transportCostDao.load(dispatchOrderId);
        List<Shipment> shipments = shipmentDao.listBaseByDispatchOrderId(dispatchOrderId);
        
        // 2. get full salary / empty salary / normal salary
        BigDecimal otherFee = BigDecimal.ZERO;
        BigDecimal totalFullSalary = transportCost.getMainDriverFullSalary().add(transportCost.getFirstViceDriverFullSalary()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal totalEmptySalary = transportCost.getMainDriverEmptySalary().add(transportCost.getFirstViceDriverEmptySalary()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal totalSalary = transportCost.getMainDriverSalary().add(transportCost.getFirstViceDriverSalary()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        
        // 3.1 define some variables
        BigDecimal totalFs = new BigDecimal(shipments.size()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal tyreFee = transportCost.getTyreFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal repairFee = transportCost.getRepairFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal roadFee = transportCost.getRoadFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal intOilFeeComp = transportCost.getInternalOilFeeCompensation().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal externalOilFee = transportCost.getExternalOilFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal oilUsage = transportCost.getOilUsage().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal facilityDistance = dispatchOrder.getFacilityDistance().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal fullLoadDistance = transportCost.getFullLoadDistance().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal internalOilUsage = transportCost.getInternalOilUsage().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal enternalOilUsage = oilUsage.subtract(internalOilUsage).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal intOilUsageComp = transportCost.getInternalOilUsageCompen().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal runFSC = BigDecimal.ZERO;
        BigDecimal runESC = BigDecimal.ZERO;
        BigDecimal inOilFee = BigDecimal.ZERO;
        
        // 3.2 定义剩余量，用于尾行逻辑(最后一辆小车)
        BigDecimal facilityDistance2 = dispatchOrder.getFacilityDistance().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal fullLoadDistance2 = transportCost.getFullLoadDistance().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal internalOilUsage2 = transportCost.getInternalOilUsage().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal enternalOilUsage2 = oilUsage.subtract(internalOilUsage).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal externalOilFee2 = transportCost.getExternalOilFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal intOilUsageComp2 = transportCost.getInternalOilUsageCompen().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal intOilFeeComp2 = transportCost.getInternalOilFeeCompensation().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal roadFee2 = transportCost.getRoadFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal repairFee2 = transportCost.getRepairFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal tyreFee2 = transportCost.getTyreFee().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal otherFee2 = BigDecimal.ZERO;
        
        BigDecimal inOilFee2 = BigDecimal.ZERO;
        BigDecimal runFSC2 = BigDecimal.ZERO;
        BigDecimal runESC2 = BigDecimal.ZERO; 
        
        for (int i = 0; i < shipments.size(); i++) {
            
            // get finance
            Shipment shipment = shipments.get(i);
            Finance finance = financeDao.load(shipment.getShipmentId(), shipment.getLoadNumber(), shipment.getTransCorpId());
            if (finance == null) {
                finance = financeDao.load(shipment.getShipmentId(), shipment.getTransCorpId());
            }
            
            // construct sap cost detail
            SapCostDetail costDetail = this.createSapCostDetail(shipment.getId());
            
            Shipment firstShipment = shipmentDao.loadByShipmentId(shipment.getFirstShipmentId());
            if (firstShipment != null && finance == null) {
                // *18_CR_04* 上游OTMS会漏发FINANCE,从而导致业内业务也会走入该节点，为了防止错误更改成本收入匹配的LOAD_NUMBER，加入firstFinance查询和判断
                Finance firstFinance = financeDao.load(firstShipment.getShipmentId(), firstShipment.getTransCorpId());
                if (firstFinance != null && firstFinance.getCustomerId() != null && firstFinance.getCustomerId().longValue() > 0) {
                    costDetail.editLoadNumber(firstShipment.getLoadNumber());
                }
                // *18_CR_04* 上游OTMS会漏发FINANCE,从而导致业内业务也会走入该节点，为了防止错误更改成本收入匹配的LOAD_NUMBER，加入firstFinance查询和判断
            }
            
            otherFee = new BigDecimal(costDetail.getAyqtbt());
            if (i == 0) {
                
                otherFee2 = new BigDecimal(costDetail.getAyqtbt()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                
                runFSC = totalFullSalary.multiply(new BigDecimal(costDetail.getAyzzgl()));
                runFSC2 = totalFullSalary.multiply(new BigDecimal(costDetail.getAyzzgl()));
                if (runFSC.compareTo(totalSalary) > 1) {
                    runFSC = totalSalary.divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                    runFSC2 = totalSalary.divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                }
                
                runESC = totalSalary.subtract(runFSC).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                runESC2 = totalSalary.subtract(runFSC).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                
                inOilFee = internalOilUsage.multiply(transportCost.getInternalOilPrice()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                inOilFee2 = internalOilUsage.multiply(transportCost.getInternalOilPrice()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
                
            }
            
            // init sessionid
            costDetail.editSessionId(sessionId);
            
            // 3.1 传入财务信息等
            Long financeId = 0L;
            String ayhdrq = DateFormatUtils.format(new Date(), "yyyyMMdd");
            String zydttq = DateFormatUtils.format(new Date(), "HH:mm:ss");
            costDetail.edit(financeId, ayhdrq, zydttq);
            
            // 3.2 托运客户
            Client client = genericDaoSupport.load(Client.class, shipment.getCustomerId());
            String aykhdm = client.getCode();
            costDetail.editClient(aykhdm);
            
            // 3.3 外包标识
            String aywgbz = "N";
            costDetail.editOutsourceIndentify(aywgbz);
            
            // 3.4 车辆vin码
            String ayvin = shipment.getFirstShipmentId().toString();
            costDetail.setAyvin(ayvin);
             
            // 3.5 司机工资
            String aygzdj = decimalFormat.format(totalFullSalary);
            String aygzkj = decimalFormat.format(totalEmptySalary);
            costDetail.editDriverSalary(aygzdj, aygzkj);
            
            if (i != (shipments.size() - 1)) {
                
                String aycfxs = decimalFormat.format(facilityDistance.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                facilityDistance2 = facilityDistance2.subtract(new BigDecimal(aycfxs));
                
                String aycfzz = decimalFormat.format(fullLoadDistance.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                fullLoadDistance2 = fullLoadDistance2.subtract(new BigDecimal(aycfzz));
                
                String aynbyl = decimalFormat.format(internalOilUsage.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                internalOilUsage2 = internalOilUsage2.subtract(new BigDecimal(aynbyl));
                
                String aynbyf = decimalFormat.format(inOilFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                inOilFee2 = inOilFee2.subtract(inOilFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                
                String aywbyl = decimalFormat.format(enternalOilUsage.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                enternalOilUsage2 = enternalOilUsage2.subtract(new BigDecimal(aywbyl));
                
                String ayybyf = decimalFormat.format(externalOilFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                externalOilFee2 = externalOilFee2.subtract(new BigDecimal(ayybyf));
                
                String aynbbx = decimalFormat.format(intOilUsageComp.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                intOilUsageComp2 = intOilUsageComp2.subtract(new BigDecimal(aynbbx));
                
                String aynbbxf = decimalFormat.format(intOilFeeComp.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                intOilFeeComp2 = intOilFeeComp2.subtract(new BigDecimal(aynbbxf));
                
                String ayhzyf = new BigDecimal(aynbyf).add(new BigDecimal(ayybyf)).toString();
                
                String aylqfy = decimalFormat.format(roadFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                roadFee2 = roadFee2.subtract(new BigDecimal(aylqfy));
                
                String ayxldj = decimalFormat.format(repairFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                repairFee2 = repairFee2.subtract(new BigDecimal(ayxldj));
                
                String ayltdj = decimalFormat.format(tyreFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                tyreFee2 = tyreFee2.subtract(new BigDecimal(ayltdj));
                
                String ayzjzcgz = decimalFormat.format(runFSC.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                runFSC2 = runFSC2.subtract(runFSC.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                
                String ayzjkcgz = decimalFormat.format(runESC.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                runESC2 = runESC2.subtract(runESC.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                
                String ayqtbt = decimalFormat.format(otherFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                otherFee2 = otherFee2.subtract(otherFee.divide(totalFs, 2, BigDecimal.ROUND_HALF_UP));
                
                String aycbzj = decimalFormat.format(new BigDecimal(ayhzyf).add(new BigDecimal(aylqfy)).add(new BigDecimal(ayxldj))
                                                                           .add(new BigDecimal(ayltdj)).add(new BigDecimal(ayzjzcgz))
                                                                           .add(new BigDecimal(ayzjkcgz)).add(new BigDecimal(ayqtbt)));
                
                // 3.6 运输成本
                costDetail.editTransportCost(aygzdj, aygzkj, aycfxs, aycfzz, aynbyl, aynbyf, aywbyl, ayybyf, aynbbx, aynbbxf, ayhzyf, aylqfy, ayxldj, ayltdj, ayqtbt, aycbzj);
                
                // 重车工资、空车工资
                costDetail.editVehicleSalary(ayzjzcgz, ayzjkcgz);
                
            } else {
                
                String aycfxs = decimalFormat.format(facilityDistance2);
                String aycfzz = decimalFormat.format(fullLoadDistance2);
                String aynbyl = decimalFormat.format(internalOilUsage2);
                String aynbyf = decimalFormat.format(inOilFee2);
                String aywbyl = decimalFormat.format(enternalOilUsage2);
                String ayybyf = decimalFormat.format(externalOilFee2);
                String aynbbx = decimalFormat.format(intOilUsageComp2);
                String aynbbxf = decimalFormat.format(intOilFeeComp2);
                String aylqfy = decimalFormat.format(roadFee2);
                String ayxldj = decimalFormat.format(repairFee2);
                String ayltdj = decimalFormat.format(tyreFee2);
                String ayzjzcgz = decimalFormat.format(runFSC2);
                String ayzjkcgz = decimalFormat.format(runESC2);
                String ayqtbt = decimalFormat.format(otherFee2);
                
                String ayhzyf = new BigDecimal(aynbyf).add(new BigDecimal(ayybyf)).toString();
                String aycbzj = decimalFormat.format(new BigDecimal(ayhzyf).add(new BigDecimal(aylqfy)).add(new BigDecimal(ayxldj))
                                                                           .add(new BigDecimal(ayltdj)).add(new BigDecimal(ayzjzcgz))
                                                                           .add(new BigDecimal(ayzjkcgz)).add(new BigDecimal(ayqtbt)));
                
                // 3.6 运输成本
                costDetail.editTransportCost(aygzdj, aygzkj, aycfxs, aycfzz, aynbyl, aynbyf, aywbyl, ayybyf, aynbbx, aynbbxf, ayhzyf, aylqfy, ayxldj, ayltdj, ayqtbt, aycbzj);
                
                // 重车工资、空车工资
                costDetail.editVehicleSalary(ayzjzcgz, ayzjkcgz);
                
            }
            
            // 3.7 委托方信息
            if (finance != null && finance.getConsignorId() != null) {
                
                Consignor consignor = genericDaoSupport.get(Consignor.class, finance.getConsignorId());
                if (consignor == null) {
                    logger.error("#baseExpense() - 该上游Finance无效， Finance标识shipmentFinanceId={}", finance.getShipmentFinanceId());
                    throw BusinessException.newInstance("500", "上游系统balance_code未设置，请联系上游系统管理员处理。");
                }
                
                costDetail.editClient(consignor.getCode());
                
            }
            
            genericDaoSupport.save(costDetail);
            
            if (finance == null) {

                // *18_CR_04* 上游OTMS会漏发FINANCE,从而导致业内业务报销时也会走入该节点
                finance = financeDao.load(shipment.getFirstShipmentId(), shipment.getTransCorpId());
                
                if (finance != null) {
                    // *18_CR_04* 特别的，如果是分段业务，首段收入已下发，后段收入未下发情况下，后段报销时，也能找到对应FINANCE
                    // 为了防止错将首段收入误送财务系统，加入以下判断
                    if (StringUtils.isNotBlank(finance.getLoadNumber()) && finance.getConsignorId() != null && finance.getCustomerId() == null) {
                        // 不满足“原始业外业务非第一段报销情况”,finance重置为NULL
                        finance = null;
                    }
                }
                
            }
            
            // 4. construct and save revence
            if (finance != null) {
                
                // *18_CR_04* 如果调度指令不一致，则不传收入
                String financeLoadNumber = finance.getLoadNumber();
                String shipmentLoadNumber = shipment.getLoadNumber();
                if ( StringUtils.isBlank(financeLoadNumber) || StringUtils.equals(financeLoadNumber, shipmentLoadNumber) ) {
                    
                    SapRevenueDetail existRevenue = financeDao.getRevenueByFinanceId(finance.getShipmentFinanceId());
                    if (existRevenue == null) {
                        this.revenue(finance, sessionId, 0);
                    }
                    
                }
                
            }
            
        }
        
    }
    
    /**
     * @param finance
     */
    private void revenue(Finance finance, String sessionId, int type) {
        
        // 1. create sap revenue detail
        SapRevenueDetail revenue = this.createSapRevenueDetail(finance.getId());
        
        // 2. get client
        Client client = null;
        Consignor consignor = null;
        
        Shipment shipment = genericDaoSupport.get(Shipment.class, shipmentDao.getShipmentIdByFinanceId(finance.getId()));
        if (shipment == null) {
            logger.error("#deliverOrder() - 父Finance（shipmentFinanceId={}）对应Shipment不存在", finance.getShipmentFinanceId());
            throw BusinessException.newInstance("500", "上游Finance（" + finance.getShipmentFinanceId() + "）对应的Shipment不存在，请联系上游系统（OTMS/TMS）处理。");
        }
        
        // 3.1 委托方
        if (shipment != null && shipment.getCustomerId() != null) {
            client = genericDaoSupport.load(Client.class, shipment.getCustomerId());
            if (finance.getConsignorId() == null) {
                revenue.editClient(client.getCode());
            } else {
                consignor = genericDaoSupport.load(Consignor.class, finance.getConsignorId());
                revenue.editClient(consignor.getCode());
            }
        }
        
        // 3.2 业内业外标识
        if (client != null && client.getInternal()) {
            revenue.editBusinessIndentify("X");
        } else {
            revenue.editBusinessIndentify("");
        }
        
        // 3.3 回单确认状态
        if (shipment != null && shipment.getAccomplishConfirm()) {
            String ayhdbs = "X";
            String ayhdrq = DateFormatUtils.format(shipment.getAccConfirmDate(), "yyyyMMdd");
            revenue.editAccomplishConfirm(ayhdbs, ayhdrq);
        }
        
        // aykjs
        revenue.editAykjs("N");
        
        // 3.4 暂估标识
        String zmzgbz = finance.getTemporary() ? "X" : "";
        revenue.editTemporary(zmzgbz);
        
        // 用于勾选订单送SAP
        if (type == 1) {
            
            SapRevenueDetail revenueDetail = financeDao.getRevenueByFinanceId(finance.getShipmentFinanceId());
            if (revenueDetail != null) {
                revenue.setAyggbz("U");
            } else {
                revenue.setAyggbz("I");
            }
            
            // 检查是否存在未回单确认
            Date lastDate = this.checkIsAccomplish(finance.getShipmentFinanceId(), finance.getId(), finance.getCompanyId());
            if(lastDate != null) {
                String ayhdbs = "X";
                String ayhdrq = DateFormatUtils.format(lastDate, "yyyyMMdd");
                revenue.editAccomplishConfirm(ayhdbs, ayhdrq);
            } else {
                throw BusinessException.newInstance("400", "未回单确认或子订单未销单！");
            }
            
            revenue.editAykjs("Y");
            
        }
        
        // 3.5 调度单号
        String ayhddd = "";
        if (finance.getLoadNumber() != null) {
            ayhddd = finance.getLoadNumber();
        } else {
            Shipment firstShipment = shipmentDao.loadByShipmentId(shipment.getFirstShipmentId());
            if (firstShipment != null && finance.getLoadNumber() == null) {
                ayhddd = firstShipment.getLoadNumber();
            }
        }
        revenue.editShipmentOrder(ayhddd);
        
        // 合并后调度指令号
        revenue.editMergeLoadnumber(finance.getMergeLoadnumber());
        
        // 3.6 订单创建日期
        String ayddrq = DateFormatUtils.format(shipment.getShipmentDate(), "yyyyMMdd");
        revenue.editOrderCreateDate(ayddrq);
        
        // financeId
        Long financeId = finance.getShipmentFinanceId();
        revenue.editFinanceId(financeId);
        
        // edit ayvin
        String ayvin = finance.getFirstShipmentId().toString();
        revenue.editVinCode(ayvin);
        
        // edit session id
        revenue.editSessionId(sessionId);
        
        // 3. save revenue
        genericDaoSupport.save(revenue);
        
    }
    

    /**
     * 校验是否有未回单确认调度指令
     * 
     * 未回单确认或子订单未销单！
     * @param financeId
     * @param financeCompanyId
     * @return
     */
    private Date checkIsAccomplish(Long shipmentFinanceId, Long financeId, Long financeCompanyId) {
        
        Date lastDate = null;
        
        // 1. 校验是否已全部回单
        List<Long> shipmentIds = shipmentDao.listShipmentIdByFinanceId(financeId);
        if (CollectionUtils.isNotEmpty(shipmentIds)) {
            
            List<Shipment> shipments = shipmentDao.listByItemIds(shipmentIds);
            for (Shipment shipment : shipments) {
                
                Long transCorpId = shipment.getTransCorpId();
                
                if (transCorpId.equals(financeCompanyId)) {
                    
                    Date confirmDate = shipment.getAccConfirmDate();
                    
                    if (lastDate == null) {
                        lastDate = confirmDate;
                    }
                    
                    if (confirmDate != null) {
                        if (confirmDate.after(lastDate)) {
                            lastDate = confirmDate;
                        }
                    } else {
                        return null;
                    }
                    
                }
                
            }
            
        }
        
        // 2. 校验子订单是否未销单
        List<Finance> childFinances = financeDao.listChildByShipmentFinance(shipmentFinanceId);
        if (CollectionUtils.isNotEmpty(childFinances)) {
            
            for (Finance childFinance : childFinances) {
                
                Date balancedDate = childFinance.getBalancedDate();
                
                if (lastDate == null) {
                    lastDate = balancedDate;
                }
                
                if (balancedDate != null) {
                    if (balancedDate.after(lastDate)) {
                        lastDate = balancedDate;
                    }
                } else {
                    return null;
                }
                
            }
            
        }
        
        return lastDate;
        
    }
    
    /**
     * @param shipmentId
     * @return
     */
    private SapCostDetail createSapCostDetail(Long shipmentId) {
        
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        
        SapCostDetail costDetail = new SapCostDetail();
        
        // 1. get cost little
        CostLittle costLittle = genericDaoSupport.load(CostLittle.class, shipmentId);
        
        // 原始收入订单号
        String aysrdh = costLittle.getSmOid() != null ? costLittle.getSmOid().toString() : "";
        costDetail.editShipmentOrderId(aysrdh);
        
        // 业内或业外标志
        String aynwbz = null;
        if (costLittle.getIsit() != null && costLittle.getIsit()) {
            aynwbz = "X";
        } else {
            aynwbz = "";
        }
        costDetail.editBusinessIndentify(aynwbz);
        
        // 3. 车辆vin码
        String ayvin = costLittle.getVinCode() == null ? "0" : costLittle.getVinCode().toString();
        String aysvin = costLittle.getSysVinCode();
        costDetail.editVinCode(ayvin, aysvin);
        
        // 4. 派车单号、调度单号
        String aypcd = costLittle.getSmDid();
        String ayhddd = costLittle.getSmLn();
        costDetail.editShipmentOrder(aypcd, ayhddd);
        
        // 订单创建日期
        String ayddrq = DateFormatUtils.format(costLittle.getTdoDate(), "yyyyMMdd");
        costDetail.editOrderCreateDate(ayddrq);
        
        // 5. 公司代码
        String organizationCode = costLittle.getVtcCode();
        costDetail.editOrganization(organizationCode);
        
        // 6. 制造商
        String ayzzdm = costLittle.getVmCode();
        costDetail.editManufactor(ayzzdm);
        
        // 7. 运输方式
        String ayysfs = costLittle.getSmTt();
        String ayysfsmc = costLittle.getSmTtname();
        costDetail.editTransType(ayysfs, ayysfsmc);
        
        // 8. 起始地、目的地信息
        costDetail.editLocation(costLittle);
        
        // 9. 车辆相关信息
        Vehicle vehicle = vehicleDao.loadByCode(costLittle.getVehCode());
        String teamName = null;
        if (vehicle != null) {
            Team team = genericDaoSupport.load(Team.class, vehicle.getTeamId());
            teamName = team.getName();
        }
        costDetail.editVehicle(costLittle, teamName);
        
        // 行驶公里数、重载公里数
        String ayxsgl = decimalFormat.format(costLittle.getTdoFs());
        String ayzzgl = decimalFormat.format(costLittle.getTtcFld());
        String aykzgl = decimalFormat.format(costLittle.getTtcEld());
        costDetail.editDistance(ayxsgl, ayzzgl, aykzgl);
        
        // 油耗
        String ayzzyh = decimalFormat.format(costLittle.getTtcFou());
        String aykzyh = decimalFormat.format(costLittle.getTtcEou());
        costDetail.editOilUsage(ayzzyh, aykzyh);
        
        // 运输成本信息
        costDetail.editTransportCost("0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0");
        
        // 其他费用
        BigDecimal otherFee = costLittle.getTtcOtf() != null ? costLittle.getTtcOtf() : BigDecimal.ZERO;
        String ayqtbt = decimalFormat.format(otherFee);
        costDetail.editOtherFee(ayqtbt);
        
        // ayclsl
        String ayclsl = costLittle.getSmLq().toString();
        costDetail.editClsl(ayclsl);
        
        // 11. whether disable
        costDetail.disable(false);
        
        return costDetail;
        
    }
    
    /**
     * @param financeId
     * @return
     */
    private SapRevenueDetail createSapRevenueDetail(Long financeId) {
        
        // 1. get revenue little by primary key
        RevenueLittle little = genericDaoSupport.get(RevenueLittle.class, financeId);
        if (little == null) {
            logger.error("#createSapRevenueDetail() - 该上游Finance无效， Finance.id={}", financeId);
            throw BusinessException.newInstance("500", "上游调度指令被撤销（VW_REVENUE_LITTLE），请联系上游系统管理员处理。");
        }
        
        Order order = null;
        if (little.getSmOid() != null) {
            order = genericDaoSupport.load(Order.class, little.getSmOid());
        }
        
        // 2. construct revenue
        SapRevenueDetail revenueDetail = new SapRevenueDetail();
        revenueDetail.init(little, order);
        
        return revenueDetail;
        
    }
    
}